<!-- src/components/Donut3D.vue -->
<template>
  <div class="rowFlex">
    <div class="container">
      <!-- SVG 容器 -->
      <div id="putPie" class="putPie"></div>
      <!-- 光效粒子（旋转动画） -->
      <div class="lizi"></div>
      <!-- 背景光晕 -->
      <div class="light"></div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref, watch } from 'vue'
import * as d3 from 'd3'

const props = defineProps({
  data: Array,
  default: []
})
// 模拟数据
const data = ref(props.data)

// ========================
// Donut3D 模块定义
// ========================
const Donut3D = {}

// 顶部扇形路径
function pieTop(d, rx, ry, ir) {
  if (d.endAngle - d.startAngle === 0) return 'M 0 0'
  const sx = rx * Math.cos(d.startAngle)
  const sy = ry * Math.sin(d.startAngle)
  const ex = rx * Math.cos(d.endAngle)
  const ey = ry * Math.sin(d.endAngle)
  return [
    'M', sx, sy,
    'A', rx, ry, '0', (d.endAngle - d.startAngle > Math.PI ? 1 : 0), '1', ex, ey,
    'L', ir * ex, ir * ey,
    'A', ir * rx, ir * ry, '0', (d.endAngle - d.startAngle > Math.PI ? 1 : 0), '0', ir * sx, ir * sy,
    'Z'
  ].join(' ')
}

// 外侧壁路径
function pieOuter(d, rx, ry, h) {
  const startAngle = d.startAngle > Math.PI ? Math.PI : d.startAngle
  const endAngle = d.endAngle > Math.PI ? Math.PI : d.endAngle
  const sx = rx * Math.cos(startAngle)
  const sy = ry * Math.sin(startAngle)
  const ex = rx * Math.cos(endAngle)
  const ey = ry * Math.sin(endAngle)
  return [
    'M', sx, h + sy,
    'A', rx, ry, '0 0 1', ex, h + ey,
    'L', ex, ey,
    'A', rx, ry, '0 0 0', sx, sy,
    'Z'
  ].join(' ')
}

// 内侧壁路径
function pieInner(d, rx, ry, h, ir) {
  const startAngle = d.startAngle < Math.PI ? Math.PI : d.startAngle
  const endAngle = d.endAngle < Math.PI ? Math.PI : d.endAngle
  const sx = ir * rx * Math.cos(startAngle)
  const sy = ir * ry * Math.sin(startAngle)
  const ex = ir * rx * Math.cos(endAngle)
  const ey = ir * ry * Math.sin(endAngle)
  return [
    'M', sx, sy,
    'A', ir * rx, ir * ry, '0 0 1', ex, ey,
    'L', ex, h + ey,
    'A', ir * rx, ir * ry, '0 0 0', sx, h + sy,
    'Z'
  ].join(' ')
}

// 百分比文本
function getPercent(d) {
 const value = d.data.value
  return value != null ? value.toString() : ''
}

// 过渡动画
Donut3D.transition = function(id, data, rx, ry, h, ir) {
  const pie = d3.pie().sort(null).value(d => d.value)
  const _data = pie(data)

  const arcTweenInner = function(a) {
    const i = d3.interpolate(this._current, a)
    this._current = i(0)
    return t => pieInner(i(t), rx + 0.5, ry + 0.5, h, ir)
  }

  const arcTweenTop = function(a) {
    const i = d3.interpolate(this._current, a)
    this._current = i(0)
    return t => pieTop(i(t), rx, ry, ir)
  }

  const arcTweenOuter = function(a) {
    const i = d3.interpolate(this._current, a)
    this._current = i(0)
    return t => pieOuter(i(t), rx - 0.5, ry - 0.5, h)
  }

  const textTweenX = function(a) {
    const i = d3.interpolate(this._current, a)
    this._current = i(0)
    return t => 0.6 * rx * Math.cos(0.5 * (i(t).startAngle + i(t).endAngle))
  }

  const textTweenY = function(a) {
    const i = d3.interpolate(this._current, a)
    this._current = i(0)
    return t => 0.6 * ry * Math.sin(0.5 * (i(t).startAngle + i(t).endAngle))
  }

  d3.select(`#${id}`).selectAll('.innerSlice')
    .data(_data)
    .transition().duration(750).attrTween('d', arcTweenInner)

  d3.select(`#${id}`).selectAll('.topSlice')
    .data(_data)
    .transition().duration(750).attrTween('d', arcTweenTop)

  d3.select(`#${id}`).selectAll('.outerSlice')
    .data(_data)
    .transition().duration(750).attrTween('d', arcTweenOuter)

  d3.select(`#${id}`).selectAll('.percent')
    .data(_data)
    .transition().duration(750)
    .attrTween('x', textTweenX)
    .attrTween('y', textTweenY)
    .text(getPercent)

  // ======== 在 transition 函数末尾添加 ========
// 连线动画
const lineTweenX2 = function(a) {
  const i = d3.interpolate(this._current, a)
  this._current = i(0)
  return t => {
    const angle = 0.5 * (i(t).startAngle + i(t).endAngle)
    return (rx + 30) * Math.cos(angle)
  }
}
const lineTweenY2 = function(a) {
  const i = d3.interpolate(this._current, a)
  this._current = i(0)
  return t => {
    const angle = 0.5 * (i(t).startAngle + i(t).endAngle)
    return (ry + 30) * Math.sin(angle)
  }
}

// label 文本动画
const labelTweenX = function(a) {
  const i = d3.interpolate(this._current, a)
  this._current = i(0)
  return t => {
    const angle = 0.5 * (i(t).startAngle + i(t).endAngle)
    return (rx + 40) * Math.cos(angle)
  }
}
const labelTweenY = function(a) {
  const i = d3.interpolate(this._current, a)
  this._current = i(0)
  return t => {
    const angle = 0.5 * (i(t).startAngle + i(t).endAngle)
    return (ry + 40) * Math.sin(angle)
  }
}
const labelTweenAnchor = function(a) {
  const i = d3.interpolate(this._current, a)
  this._current = i(0)
  return t => {
    const angle = 0.5 * (i(t).startAngle + i(t).endAngle)
    return angle < Math.PI ? 'start' : 'end'
  }
}

// 应用动画
d3.select(`#${id}`).selectAll('.label-line')
  .data(_data)
  .transition().duration(750)
  .attrTween('x2', lineTweenX2)
  .attrTween('y2', lineTweenY2)

d3.select(`#${id}`).selectAll('.label-text')
  .data(_data)
  .transition().duration(750)
  .attrTween('x', labelTweenX)
  .attrTween('y', labelTweenY)
  .attrTween('text-anchor', labelTweenAnchor)
  .text(d => d.data.label)

}

// 绘制
Donut3D.draw = function(id, data, x, y, rx, ry, h, ir) {
  const pie = d3.pie().sort(null).value(d => d.value)
  const _data = pie(data)

  const slices = d3.select(`#${id}`)
    .append('g')
    .attr('transform', `translate(${x},${y})`)
    .attr('class', 'slices')

  // 内壁
  slices.selectAll('.innerSlice')
    .data(_data)
    .enter().append('path')
    .attr('class', 'innerSlice')
    .style('fill', d => d3.hsl(d.data.color).darker(0.7))
    .attr('d', d => pieInner(d, rx + 0.5, ry + 0.5, h, ir))
    .each(function(d) { this._current = d })

  // 顶面
  slices.selectAll('.topSlice')
    .data(_data)
    .enter().append('path')
    .attr('class', 'topSlice')
    .style('fill', d => d.data.color)
    .style('stroke', d => d.data.color)
    .attr('d', d => pieTop(d, rx, ry, ir))
    .each(function(d) { this._current = d })

  // 外壁
  slices.selectAll('.outerSlice')
    .data(_data)
    .enter().append('path')
    .attr('class', 'outerSlice')
    .style('fill', d => d3.hsl(d.data.color).darker(0.7))
    .attr('d', d => pieOuter(d, rx - 0.5, ry - 0.5, h))
    .each(function(d) { this._current = d })

  // 百分比文字
  slices.selectAll('.percent')
    .data(_data)
    .enter().append('text')
    .attr('class', 'percent')
    .attr('x', d => 0.6 * rx * Math.cos(0.5 * (d.startAngle + d.endAngle)))
    .attr('y', d => 0.6 * ry * Math.sin(0.5 * (d.startAngle + d.endAngle)))
    .text(getPercent)
    .each(function(d) { this._current = d })

  // 添加 label 文本和连线
slices.selectAll('.label-line')
  .data(_data)
  .enter().append('line')
  .attr('class', 'label-line')
  .style('stroke', '#fff')
  .style('stroke-width', 1)
  .attr('x1', d => rx * Math.cos(0.5 * (d.startAngle + d.endAngle)))
  .attr('y1', d => ry * Math.sin(0.5 * (d.startAngle + d.endAngle)))
  .attr('x2', d => {
    const angle = 0.5 * (d.startAngle + d.endAngle)
    return (rx + 30) * Math.cos(angle)
  })
  .attr('y2', d => {
    const angle = 0.5 * (d.startAngle + d.endAngle)
    return (ry + 30) * Math.sin(angle)
  })

slices.selectAll('.label-text')
  .data(_data)
  .enter().append('text')
  .attr('class', 'label-text')
  .attr('x', d => {
    const angle = 0.5 * (d.startAngle + d.endAngle)
    return (rx + 40) * Math.cos(angle)
  })
  .attr('y', d => {
    const angle = 0.5 * (d.startAngle + d.endAngle)
    return (ry + 40) * Math.sin(angle)
  })
  .attr('dy', '0.35em')
  .attr('text-anchor', d => {
    const angle = 0.5 * (d.startAngle + d.endAngle)
    return angle < Math.PI ? 'start' : 'end'
  })
  .style('fill', '#fff')
  .style('font-size', '12px')
  .text(d => d.data.label)
}

// ========================
// 生命周期
// ========================
onMounted(() => {
  // 创建 SVG
  const svg = d3.select('#putPie')
    .append('svg')
    .attr('width', 400)
    .attr('height', 340)

  svg.append('g').attr('id', 'demoPie')

  // 初始绘制
  Donut3D.draw('demoPie', data.value, 200, 200, 112, 50, 20, 0.8)

  // 每 4 秒更新数据
  setInterval(() => {
    const newData = data.value.map(d => ({
      ...d,
      value: Math.floor(66 + Math.random() * 21)
    }))
    Donut3D.transition('demoPie', newData, 112, 50, 20, 0.8)
  }, 4000)
})
</script>
<style>
@keyframes rotate {
  0%   { background-position-y: 0px; }
  100% { background-position-y: -4620px; }
}
</style>
<style scoped>
/* 布局 */
.rowFlex {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
  padding: 0;
}

.container {
  position: relative;
  width: 400px;
  height: 300px;
}

.putPie {
  position: absolute;
  top: -43px;
  left: 0;
  width: 400px;
  height: 300px;
  z-index: 3;
}

/* 光效动画 */
.lizi {
  position: absolute;
  top: 120px;
  left: 60px;
  z-index: 2;
  width: 280px;
  height: 132px;
  background: url('https://cdn.jsdelivr.net/gh/13428903911/my-images@main/guang.png') no-repeat 0px 0px;
  animation: rotate 1s infinite steps(35, end);
}


/* 背景光晕 */
.light {
  position: absolute;
  top: -37px;
  left: 0;
  width: 400px;
  height: 400px;
  background: url('https://cdn.jsdelivr.net/gh/13428903911/my-images@main/light.png') no-repeat center 135px;
  z-index: 1;
}

/* ✅ 使用 :deep() 控制 SVG 内部元素 */
:deep(svg text.percent) {
  fill: #fff !important;
  font-size: 14px;
  font-family: Arial, sans-serif;
  text-anchor: middle;
}

:deep(svg .label-line) {
  stroke: #fff;
  stroke-width: 1;
  opacity: 0.7;
}

:deep(svg .label-text) {
  fill: #fff;
  font-size: 12px;
  font-family: Arial, sans-serif;
  opacity: 0.9;
}
</style>